This updates Cargo for #[crate_name]
authorYehuda Katz + Carl Lerche <engineering@tilde.io>
Tue, 8 Jul 2014 00:59:18 +0000 (17:59 -0700)
committerYehuda Katz <wycats@gmail.com>
Tue, 8 Jul 2014 04:42:34 +0000 (21:42 -0700)
src/bin/cargo-build.rs
src/bin/cargo-test.rs
src/cargo/core/manifest.rs
src/cargo/lib.rs
src/cargo/ops/cargo_read_manifest.rs
src/cargo/ops/cargo_rustc.rs
src/cargo/util/toml.rs
tests/test_cargo_compile.rs

index 04e2ec790cfb33b671112bbda5f2c654e4e96734..d0530615c4eb0d204e70f88d243b2711ecc78a80 100755 (executable)
@@ -49,9 +49,6 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
                     }))
     };
 
-    let update = options.update_remotes;
-    let jobs = options.jobs;
-
     let env = if options.release {
         "release"
     } else {
index 79624e47cf27efdfa4e7f1a6974c7db1856285ff..257b7024ef72a9e55ecf15e2714c75d6c811d9fa 100755 (executable)
@@ -9,7 +9,7 @@ extern crate serialize;
 extern crate hammer;
 
 use std::os;
-use std::io::fs;
+use std::io::{UserExecute, fs};
 
 use cargo::ops;
 use cargo::{execute_main_without_stdin};
@@ -65,7 +65,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
         // TODO: The proper fix is to have target knows its expected
         // output and only run expected executables.
         if file.display().to_str().as_slice().contains("dSYM") { continue; }
-        if !file.is_file() { continue; }
+        if !is_executable(&file) { continue; }
 
         try!(util::process(file).exec().map_err(|e| {
             CliError::from_boxed(e.box_error(), 1)
@@ -74,3 +74,8 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
 
     Ok(None)
 }
+
+fn is_executable(path: &Path) -> bool {
+    if !path.is_file() { return false; }
+    path.stat().map(|stat| stat.perm.intersects(UserExecute)).unwrap_or(false)
+}
index 8a6223061bf97186d0fe81bb89969d0f9c036258..96cf45739f71f9a7941fb88c01989100b8c790e4 100644 (file)
@@ -310,6 +310,10 @@ impl Target {
         }
     }
 
+    pub fn get_name<'a>(&'a self) -> &'a str {
+        self.name.as_slice()
+    }
+
     pub fn get_path<'a>(&'a self) -> &'a Path {
         &self.path
     }
index 32a07fd9c1311e619eca3a47608b998c4dc13ac9..0982ed96f4fbafb38e43d205e14b0d5861213cf8 100644 (file)
@@ -8,6 +8,7 @@ extern crate term;
 extern crate url;
 extern crate serialize;
 extern crate semver;
+extern crate glob;
 extern crate toml;
 
 #[phase(plugin, link)]
index 83eeca006f746e746ea9a724094d89072c9891ca..cd40e866a3c2dfe70815cc2598667e6eb2255685 100644 (file)
@@ -4,11 +4,12 @@ use util;
 use core::{Package,Manifest,SourceId};
 use util::{CargoResult, human};
 use util::important_paths::find_project_manifest_exact;
+use util::toml::{Layout, project_layout};
 
-pub fn read_manifest(contents: &[u8], source_id: &SourceId)
+pub fn read_manifest(contents: &[u8], layout: Layout, source_id: &SourceId)
     -> CargoResult<(Manifest, Vec<Path>)>
 {
-    util::toml::to_manifest(contents, source_id).map_err(human)
+    util::toml::to_manifest(contents, source_id, layout).map_err(human)
 }
 
 pub fn read_package(path: &Path, source_id: &SourceId)
@@ -17,8 +18,10 @@ pub fn read_package(path: &Path, source_id: &SourceId)
     log!(5, "read_package; path={}; source-id={}", path.display(), source_id);
     let mut file = try!(File::open(path));
     let data = try!(file.read_to_end());
-    let (manifest, nested) = try!(read_manifest(data.as_slice(),
-                                                      source_id));
+
+    let layout = project_layout(&path.dir_path());
+    let (manifest, nested) = 
+        try!(read_manifest(data.as_slice(), layout, source_id));
 
     Ok((Package::new(manifest, path, source_id), nested))
 }
index 6083932a3b348c270ea4649e83f8ec9510ce55a9..18b2edc20bbafc64f3940ca6fe1fd7ef243be40a 100644 (file)
@@ -219,7 +219,7 @@ fn rustc(root: &Path, target: &Target, cx: &mut Context) -> Job {
 
     log!(5, "command={}", rustc);
 
-    cx.config.shell().verbose(|shell| shell.status("Running", rustc.to_str()));
+    let _ = cx.config.shell().verbose(|shell| shell.status("Running", rustc.to_str()));
 
     proc() {
         if primary {
@@ -249,12 +249,16 @@ fn build_base_args(into: &mut Args, target: &Target, crate_types: Vec<&str>,
                    cx: &Context) {
     // TODO: Handle errors in converting paths into args
     into.push(target.get_path().display().to_str());
+
+    into.push("--crate-name".to_str());
+    into.push(target.get_name().to_str());
+
     for crate_type in crate_types.iter() {
         into.push("--crate-type".to_str());
         into.push(crate_type.to_str());
     }
 
-    let mut out = cx.dest.clone();
+    let out = cx.dest.clone();
     let profile = target.get_profile();
 
     if profile.get_opt_level() != 0 {
@@ -270,8 +274,13 @@ fn build_base_args(into: &mut Args, target: &Target, crate_types: Vec<&str>,
         into.push("--test".to_str());
     }
 
-    into.push("--out-dir".to_str());
-    into.push(out.display().to_str());
+    if target.is_lib() {
+        into.push("--out-dir".to_str());
+        into.push(out.display().to_str());
+    } else {
+        into.push("-o".to_str());
+        into.push(out.join(target.get_name()).display().to_str());
+    }
 }
 
 fn build_deps_args(dst: &mut Args, cx: &Context) {
index 8de333e6ae29c18daa6e810714fca778f9b7a3cc..59fba60d8364467917efa786f1fa36453a7663f2 100644 (file)
@@ -3,14 +3,58 @@ use std::collections::HashMap;
 use std::str;
 use toml;
 
+use glob::glob;
 use core::{SourceId, GitKind};
 use core::manifest::{LibKind, Lib, Profile};
 use core::{Summary, Manifest, Target, Dependency, PackageId};
 use core::source::Location;
 use util::{CargoResult, Require, human};
 
+#[deriving(Clone)]
+pub struct Layout {
+    lib: Option<Path>,
+    bins: Vec<Path>
+}
+
+impl Layout {
+    fn main<'a>(&'a self) -> Option<&'a Path> {
+        self.bins.iter().find(|p| {
+            match p.filename_str() {
+                Some(s) => s == "main.rs",
+                None => false
+            }
+        })
+    }
+}
+
+pub fn project_layout(root: &Path) -> Layout {
+    let mut lib = None;
+    let mut bins = vec!();
+
+    if root.join("src/lib.rs").exists() {
+        lib = Some(root.join("src/lib.rs"));
+    }
+
+    if root.join("src/main.rs").exists() {
+        bins.push(root.join("src/main.rs"));
+    }
+
+    // TODO: glob takes a &str even though Paths may have non-UTF8 chars. This
+    // seems like a bug in libglob
+    let found = glob(root.join("src/bin/*.rs").display().to_str().as_slice()).collect();
+    bins.push_all_move(found);
+
+    Layout {
+        lib: lib,
+        bins: bins
+    }
+}
+
 pub fn to_manifest(contents: &[u8],
-                   source_id: &SourceId) -> CargoResult<(Manifest, Vec<Path>)> {
+                   source_id: &SourceId,
+                   layout: Layout)
+                   -> CargoResult<(Manifest, Vec<Path>)>
+{
     let contents = try!(str::from_utf8(contents).require(|| {
         human("Cargo.toml is not valid UTF-8")
     }));
@@ -22,7 +66,7 @@ pub fn to_manifest(contents: &[u8],
                                             manifest\n\n{}", e)))
     };
 
-    let pair = try!(toml_manifest.to_manifest(source_id).map_err(|err| {
+    let pair = try!(toml_manifest.to_manifest(source_id, &layout).map_err(|err| {
         human(format!("Cargo.toml is not a valid manifest\n\n{}", err))
     }));
     let (mut manifest, paths) = pair;
@@ -51,8 +95,6 @@ pub fn to_manifest(contents: &[u8],
             _ => m.add_unused_key(key),
         }
     }
-
-
 }
 
 pub fn parse(toml: &str, file: &str) -> CargoResult<toml::Table> {
@@ -135,16 +177,98 @@ struct Context<'a> {
     nested_paths: &'a mut Vec<Path>
 }
 
+// These functions produce the equivalent of specific manifest entries. One
+// wrinkle is that certain paths cannot be represented in the manifest due
+// to Toml's UTF-8 requirement. This could, in theory, mean that certain
+// otherwise acceptable executable names are not used when inside of
+// `src/bin/*`, but it seems ok to not build executables with non-UTF8
+// paths.
+fn inferred_lib_target(name: &str, layout: &Layout) -> Option<Vec<TomlTarget>> {
+    layout.lib.as_ref().map(|lib| {
+        vec![TomlTarget {
+            name: name.to_str(),
+            crate_type: None,
+            path: Some(lib.display().to_str()),
+            test: None
+        }]
+    })
+}
+
+fn inferred_bin_targets(name: &str, layout: &Layout) -> Option<Vec<TomlTarget>> {
+    Some(layout.bins.iter().filter_map(|bin| {
+        let name = if bin.as_str() == Some("src/main.rs") {
+            Some(name.to_str())
+        } else {
+            bin.filestem_str().map(|f| f.to_str())
+        };
+
+        name.map(|name| {
+            TomlTarget {
+                name: name,
+                crate_type: None,
+                path: Some(bin.display().to_str()),
+                test: None
+            }
+        })
+    }).collect())
+}
+
 impl TomlManifest {
-    pub fn to_manifest(&self, source_id: &SourceId)
+    pub fn to_manifest(&self, source_id: &SourceId, layout: &Layout)
         -> CargoResult<(Manifest, Vec<Path>)>
     {
         let mut sources = vec!();
         let mut nested_paths = vec!();
 
+        let project = self.project.as_ref().or_else(|| self.package.as_ref());
+        let project = try!(project.require(|| {
+            human("No `package` or `project` section found.")
+        }));
+
+
+        // If we have no lib at all, use the inferred lib if available
+        // If we have a lib with a path, we're done
+        // If we have a lib with no path, use the inferred lib or_else package name
+
+        let lib = if self.lib.is_none() || self.lib.get_ref().is_empty() {
+            inferred_lib_target(project.name.as_slice(), layout)
+        } else {
+            Some(self.lib.get_ref().iter().map(|t| {
+                if layout.lib.is_some() && t.path.is_none() {
+                    TomlTarget {
+                        name: t.name.clone(),
+                        crate_type: t.crate_type.clone(),
+                        path: layout.lib.as_ref().map(|p| p.display().to_str()),
+                        test: t.test
+                    }
+                } else {
+                    t.clone()
+                }
+            }).collect())
+        };
+
+        let bins = if self.bin.is_none() || self.bin.get_ref().is_empty() {
+            inferred_bin_targets(project.name.as_slice(), layout)
+        } else {
+            let bin = layout.main();
+
+            Some(self.bin.get_ref().iter().map(|t| {
+                if bin.is_some() && t.path.is_none() {
+                    TomlTarget {
+                        name: t.name.clone(),
+                        crate_type: t.crate_type.clone(),
+                        path: bin.as_ref().map(|p| p.display().to_str()),
+                        test: t.test
+                    }
+                } else {
+                    t.clone()
+                }
+            }).collect())
+        };
+
         // Get targets
-        let targets = normalize(self.lib.as_ref().map(|l| l.as_slice()),
-                                self.bin.as_ref().map(|b| b.as_slice()));
+        let targets = normalize(lib.as_ref().map(|l| l.as_slice()),
+                                bins.as_ref().map(|b| b.as_slice()));
 
         if targets.is_empty() {
             debug!("manifest has no build targets; project={}", self.project);
@@ -166,11 +290,6 @@ impl TomlManifest {
             try!(process_dependencies(&mut cx, true, self.dev_dependencies.as_ref()));
         }
 
-        let project = self.project.as_ref().or_else(|| self.package.as_ref());
-        let project = try!(project.require(|| {
-            human("No `package` or `project` section found.")
-        }));
-
         let pkgid = try!(project.to_package_id(source_id.get_location()));
         let summary = Summary::new(&pkgid, deps.as_slice());
         Ok((Manifest::new(
@@ -243,7 +362,9 @@ struct TomlTarget {
 }
 
 fn normalize(lib: Option<&[TomlLibTarget]>,
-             bin: Option<&[TomlBinTarget]>) -> Vec<Target> {
+             bin: Option<&[TomlBinTarget]>)
+             -> Vec<Target>
+{
     log!(4, "normalizing toml targets; lib={}; bin={}", lib, bin);
 
     fn target_profiles(target: &TomlTarget) -> Vec<Profile> {
index 9b6e3b816cecc8af2513cc7d3d1262069d486241..c2e9e6bec76946dc4f75169bce8c723e03df1be5 100644 (file)
@@ -92,8 +92,8 @@ test!(cargo_compile_with_invalid_code {
 {filename}:1 invalid rust code!
              ^~~~~~~
 Could not execute process \
-`rustc {filename} --crate-type bin -g --out-dir {} -L {} -L {}` (status=101)\n",
-            target.display(),
+`rustc {filename} --crate-name foo --crate-type bin -g -o {} -L {} -L {}` (status=101)\n",
+            target.join("foo").display(),
             target.display(),
             target.join("deps").display(),
             filename = format!("src{}foo.rs", path::SEP)).as_slice()));
@@ -176,6 +176,142 @@ test!(cargo_compile_with_warnings_in_a_dep_package {
       execs().with_stdout("test passed\n"));
 })
 
+test!(cargo_compile_with_nested_deps_inferred {
+    let mut p = project("foo");
+    let bar = p.root().join("bar");
+    let baz = p.root().join("baz");
+
+    p = p
+        .file(".cargo/config", format!(r#"
+            paths = ["{}", "{}"]
+        "#, escape_path(&bar), escape_path(&baz)).as_slice())
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies]
+
+            bar = "0.5.0"
+
+            [[bin]]
+
+            name = "foo"
+        "#)
+        .file("src/foo.rs",
+              main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies]
+
+            baz = "0.5.0"
+        "#)
+        .file("bar/src/lib.rs", r#"
+            extern crate baz;
+
+            pub fn gimme() -> String {
+                baz::gimme()
+            }
+        "#)
+        .file("baz/Cargo.toml", r#"
+            [project]
+
+            name = "baz"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("baz/src/lib.rs", r#"
+            pub fn gimme() -> String {
+                "test passed".to_str()
+            }
+        "#);
+
+    p.cargo_process("cargo-build")
+        .exec_with_output()
+        .assert();
+
+    assert_that(&p.bin("foo"), existing_file());
+
+    assert_that(
+      cargo::util::process(p.bin("foo")),
+      execs().with_stdout("test passed\n"));
+})
+
+test!(cargo_compile_with_nested_deps_correct_bin {
+    let mut p = project("foo");
+    let bar = p.root().join("bar");
+    let baz = p.root().join("baz");
+
+    p = p
+        .file(".cargo/config", format!(r#"
+            paths = ["{}", "{}"]
+        "#, escape_path(&bar), escape_path(&baz)).as_slice())
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies]
+
+            bar = "0.5.0"
+
+            [[bin]]
+
+            name = "foo"
+        "#)
+        .file("src/main.rs",
+              main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies]
+
+            baz = "0.5.0"
+        "#)
+        .file("bar/src/lib.rs", r#"
+            extern crate baz;
+
+            pub fn gimme() -> String {
+                baz::gimme()
+            }
+        "#)
+        .file("baz/Cargo.toml", r#"
+            [project]
+
+            name = "baz"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("baz/src/lib.rs", r#"
+            pub fn gimme() -> String {
+                "test passed".to_str()
+            }
+        "#);
+
+    p.cargo_process("cargo-build")
+        .exec_with_output()
+        .assert();
+
+    assert_that(&p.bin("foo"), existing_file());
+
+    assert_that(
+      cargo::util::process(p.bin("foo")),
+      execs().with_stdout("test passed\n"));
+})
+
 test!(cargo_compile_with_nested_deps_shorthand {
     let mut p = project("foo");
     let bar = p.root().join("bar");
@@ -511,18 +647,16 @@ test!(custom_build_in_dependency {
             version = "0.5.0"
             authors = ["wycats@example.com"]
             build = "{}"
-
-            [[lib]]
-            name = "bar"
         "#, escape_path(&build.bin("foo"))))
-        .file("bar/src/bar.rs", r#"
+        .file("bar/src/lib.rs", r#"
             pub fn bar() {}
         "#);
     assert_that(p.cargo_process("cargo-build"),
                 execs().with_status(0));
 })
 
-test!(many_crate_types {
+// this is testing that src/<pkg-name>.rs still works (for now)
+test!(many_crate_types_old_style_lib_location {
     let mut p = project("foo");
     p = p
         .file("Cargo.toml", r#"
@@ -560,6 +694,44 @@ test!(many_crate_types {
             file1.ends_with(os::consts::DLL_SUFFIX));
 })
 
+test!(many_crate_types_correct {
+    let mut p = project("foo");
+    p = p
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [[lib]]
+
+            name = "foo"
+            crate_type = ["rlib", "dylib"]
+        "#)
+        .file("src/lib.rs", r#"
+            pub fn foo() {}
+        "#);
+    assert_that(p.cargo_process("cargo-build"),
+                execs().with_status(0));
+
+    let files = fs::readdir(&p.root().join("target")).assert();
+    let mut files: Vec<String> = files.iter().filter_map(|f| {
+        match f.filename_str().unwrap() {
+            "deps" => None,
+            s if s.contains("fingerprint") || s.contains("dSYM") => None,
+            s => Some(s.to_str())
+        }
+    }).collect();
+    files.sort();
+    let file0 = files.get(0).as_slice();
+    let file1 = files.get(1).as_slice();
+    println!("{} {}", file0, file1);
+    assert!(file0.ends_with(".rlib") || file1.ends_with(".rlib"));
+    assert!(file0.ends_with(os::consts::DLL_SUFFIX) ||
+            file1.ends_with(os::consts::DLL_SUFFIX));
+})
+
 test!(unused_keys {
     let mut p = project("foo");
     p = p